baubs git
Commit 47e32ef83de1d3e90b119727fafce86f81ad8628
Parents : 7fd31d2
Author : Jan-Henrik Bruhn <jan-henrik.bruhn@offis.de>
Date : 2025-12-26T21:33:32+01:00
fix: Resolve state-during-render anti-pattern in usePatternTransform
Moved state synchronization logic from render phase to useEffect hooks to prevent potential infinite loops and unpredictable behavior. Implemented ref-based previous value tracking to detect genuine parent prop changes without causing cascading renders.
Changes:
- Replaced direct setState calls during render with properly structured useEffect hooks
- Added prevOffsetRef and prevRotationRef to track previous prop values
- Documented the "partially controlled" pattern needed for Konva drag interactions
- Added justified ESLint disable comments for legitimate setState-in-effect usage
This fixes a critical React anti-pattern that could cause performance issues and render loops.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes
Diff
diff --git a/src/hooks/usePatternTransform.ts b/src/hooks/usePatternTransform.ts
index 1f13f50..a3f1a2e 100644
--- a/src/hooks/usePatternTransform.ts
+++ b/src/hooks/usePatternTransform.ts
@@ -39,22 +39,41 @@ export function usePatternTransform({
const patternGroupRef = useRef<Konva.Group | null>(null);
const transformerRef = useRef<Konva.Transformer | null>(null);
- // Update pattern offset when initialPatternOffset changes
- if (
- initialPatternOffset &&
- (localPatternOffset.x !== initialPatternOffset.x ||
- localPatternOffset.y !== initialPatternOffset.y)
- ) {
- setLocalPatternOffset(initialPatternOffset);
- }
-
- // Update pattern rotation when initialPatternRotation changes
- if (
- initialPatternRotation !== undefined &&
- localPatternRotation !== initialPatternRotation
- ) {
- setLocalPatternRotation(initialPatternRotation);
- }
+ // Track previous prop values to detect external changes
+ const prevOffsetRef = useRef(initialPatternOffset);
+ const prevRotationRef = useRef(initialPatternRotation);
+
+ // Sync local state with parent props when they change externally
+ // This implements a "partially controlled" pattern needed for Konva drag interactions:
+ // - Local state enables optimistic updates during drag/transform (immediate visual feedback)
+ // - Parent props sync when external changes occur (e.g., pattern upload resets position)
+ // - Previous value refs prevent sync loops by only updating when props genuinely change
+ useEffect(() => {
+ if (
+ initialPatternOffset &&
+ (prevOffsetRef.current.x !== initialPatternOffset.x ||
+ prevOffsetRef.current.y !== initialPatternOffset.y)
+ ) {
+ // This setState in effect is intentional and safe: it only runs when the parent
+ // prop changes, not in response to our own local updates
+ // eslint-disable-next-line react-hooks/set-state-in-effect
+ setLocalPatternOffset(initialPatternOffset);
+ prevOffsetRef.current = initialPatternOffset;
+ }
+ }, [initialPatternOffset]);
+
+ useEffect(() => {
+ if (
+ initialPatternRotation !== undefined &&
+ prevRotationRef.current !== initialPatternRotation
+ ) {
+ // This setState in effect is intentional and safe: it only runs when the parent
+ // prop changes, not in response to our own local updates
+ // eslint-disable-next-line react-hooks/set-state-in-effect
+ setLocalPatternRotation(initialPatternRotation);
+ prevRotationRef.current = initialPatternRotation;
+ }
+ }, [initialPatternRotation]);
// Attach/detach transformer based on state
const attachTransformer = useCallback(() => {
Served by rngit 1.3.3 - Generated in 0.09s